package com.ml.netty.demo;
 
import com.alibaba.fastjson.JSONObject;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.channel.unix.DomainSocketAddress;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslCloseCompletionEvent;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

/**
 * 参考资料：https://www.cnblogs.com/daijiting/p/12092202.html  消息发送回调
 *  https://blog.csdn.net/qq_38215042/article/details/125747184  wss握手
 *  https://blog.csdn.net/qq_42875345/article/details/122128440  心跳机制
 *  https://zhuanlan.zhihu.com/p/38320072 客户端维持心跳
 * 要链接wss必须带握手用于协议升级
 * @author dzx
 * @ClassName:
 * @Description: netty客户端
 * @date 2023年06月30日 21:30:02
 */
@Slf4j
public class QQSocketClient1 {
    // 服务端IP
    static final String url = "wss://sandbox.api.sgroup.qq.com/websocket";
 
    // 主函数启动
    public static void main(String[] args) throws InterruptedException, URISyntaxException {
        sendMessage();
    }
    /**
     * 核心方法（处理：服务端向客户端发送的数据、客户端向服务端发送的数据）
     */
    public static void sendMessage() throws InterruptedException, URISyntaxException {
            // 创建事件循环组和通道类型
            EventLoopGroup group = new NioEventLoopGroup(5);
        try {
            NettyWebSocketClientHandler handler = new NettyWebSocketClientHandler();
            //websocke连接的地址，/hello是因为在服务端的websockethandler设置的
            URI websocketURI = new URI("wss://sandbox.api.sgroup.qq.com:443/websocket");
            WebSocketClientHandshaker handshaker = WebSocketClientHandshakerFactory
                    .newHandshaker(websocketURI, WebSocketVersion.V13, (String)null, false, new DefaultHttpHeaders());
            System.out.println(websocketURI.getScheme());
            System.out.println(websocketURI.getHost());
            System.out.println(websocketURI.getPort());
            SslContext sslCtx = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();

                // 创建引导程序并设置参数
                Bootstrap bootstrap = new Bootstrap();
                bootstrap.group(group)
                        .channel(NioSocketChannel.class)
                        .option(ChannelOption.SO_KEEPALIVE, true)
                        .option(ChannelOption.TCP_NODELAY, true)
                        .handler(new LoggingHandler(LogLevel.INFO))
                        .handler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            public void initChannel(SocketChannel ch) throws Exception {
                                // 添加协议处理器和帧处理器等

                                ChannelPipeline entries = ch.pipeline()
                                        .addLast(new HttpClientCodec())//http的编解码器
                                        .addLast(new ChunkedWriteHandler())// 添加一个用于支持大数据流的支持
                                        // 添加一个聚合器，这个聚合器主要是将HttpMessage聚合成FullHttpRequest/Response
                                        .addLast(new HttpObjectAggregator(8192))
                                        .addLast(handler)
                                        .addFirst(sslCtx.newHandler(ch.alloc(), websocketURI.getHost(),websocketURI.getPort()))
                                        .addLast(new WebSocketServerProtocolHandler("/websocket", null, true, 65536 * 10))
//                                        .addLast(new IdleStateHandler(0,5,0,TimeUnit.SECONDS))
                                        ;
                            }
                        });


            //客户端与服务端连接的通道，final修饰表示只会有一个
            final Channel channel=bootstrap.connect(websocketURI.getHost(),websocketURI.getPort()).sync().channel();
            handler.setHandshaker(handshaker);
            handshaker.handshake(channel);
//            //发送消息
//            System.out.println("发送消息");
//            JSONObject hhh = new JSONObject();
//            hhh.put("cmd","test");
//            for (int i = 0; i < 5; i++) {
//                Thread.sleep(2000);
//                channel.writeAndFlush(new TextWebSocketFrame(hhh.toString()));
//            }
//            String message = "{\"op\":2,\"d\":{\"token\":\"Bot 102082942.2LLRU234TSXVfOVGg02Ht8Eab83lxu7I\",\"intents\":513,\"shard\":[0,1]}}";
//            channel.writeAndFlush(new TextWebSocketFrame(message));
            //阻塞等待是否握手成功
            handler.handshakeFuture().sync();
            System.out.println("握手成功");

//            //发送消息
//            System.out.println("发送消息");
//            JSONObject clientJson = new JSONObject();
//            clientJson.put("cmd","test");
//            channel.writeAndFlush(new TextWebSocketFrame(clientJson.toString()));

//            message = "{\"op\":2,\"d\":{\"token\":\"Bot 102082942.2LLRU234TSXVfOVGg02Ht8Eab83lxu7I\",\"intents\":513,\"shard\":[0,1]}}";
//            channel.writeAndFlush(new TextWebSocketFrame(message));
            // 等待连接被关闭
            channel.closeFuture().sync();
                // 处理连接成功或失败的情况，例如关闭连接、处理异常等。这里可以根据实际需求进行相应的处理。
            } catch (Exception e) {
            System.out.println("有异常了");
                e.printStackTrace();
            } finally {
                // 关闭事件循环组和释放资源
                group.shutdownGracefully();
            }
        }

    public static class NettyWebSocketClientHandler extends SimpleChannelInboundHandler<Object> {

        private ChannelHandlerContext channel;
        //握手的状态信息
        private  WebSocketClientHandshaker handshaker;
        //netty自带的异步处理
        private  ChannelPromise handshakeFuture;

        private ScheduledFuture<?> heartBeat;

        private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);


        @Override
        protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
            System.out.println("channelRead0 in");
            // 处理接收到的WebSocket帧数据，例如解析消息、执行逻辑等。这里可以根据实际需求进行相应的处。
            System.out.println("当前握手的状态"+this.handshaker.isHandshakeComplete());
            Channel ch = ctx.channel();
            FullHttpResponse response;
            //进行握手操作
            if (!this.handshaker.isHandshakeComplete()) {
                try {
                    response = (FullHttpResponse)msg;
                    //握手协议返回，设置结束握手
                    this.handshaker.finishHandshake(ch, response);
                    System.out.println("WebSocket Client connected!");
                    //设置成功
                    this.handshakeFuture.setSuccess();
                    System.out.println("服务端的消息"+response.headers());
                } catch (WebSocketHandshakeException var7) {
                    var7.printStackTrace();
                    FullHttpResponse res = (FullHttpResponse)msg;
                    String errorMsg = String.format("握手失败,status:%s,reason:%s", res.status(), res.content().toString(CharsetUtil.UTF_8));
                    this.handshakeFuture.setFailure(var7);
                }
            } else if (msg instanceof FullHttpResponse) {
                response = (FullHttpResponse)msg;
                throw new IllegalStateException("Unexpected FullHttpResponse (getStatus=" + response.status() + ", content=" + response.content().toString(CharsetUtil.UTF_8) + ')');
            } else {
                //接收服务端的消息
                WebSocketFrame frame = (WebSocketFrame)msg;
                //文本信息
                if (frame instanceof TextWebSocketFrame) {
                    TextWebSocketFrame textFrame = (TextWebSocketFrame)frame;
                    String text = textFrame.text();
                    System.out.println("客户端接收的消息是:"+text);
                    if(text.contains("heartbeat_interval")) {
                        log.info("auth start...");
                        int event = 4096 | 513; //频道消息和频道私信
                        String message = "{\"op\":2,\"d\":{\"token\":\"Bot 102082942.2LLRU234TSXVfOVGg02Ht8Eab83lxu7I\",\"intents\":"+event+",\"shard\":[0,1]}}";
                        ctx.writeAndFlush(new TextWebSocketFrame(message));
                    }
                    if (text.contains("READY")) {
                       log.info("auth success start heartbeat...");
                        this.heartBeat = this.scheduler.scheduleWithFixedDelay(new HeartBeatTask(ctx), 0, 30, TimeUnit.SECONDS);
                    }
                }
                //二进制信息
                if (frame instanceof BinaryWebSocketFrame) {
                    BinaryWebSocketFrame binFrame = (BinaryWebSocketFrame)frame;
                    System.out.println("BinaryWebSocketFrame");
                }
                //ping信息
                if (frame instanceof PongWebSocketFrame) {
                    System.out.println("WebSocket Client received pong");
                }
                //关闭消息
                if (frame instanceof CloseWebSocketFrame) {
                    System.out.println("receive close frame");
                    ch.close();
                }

            }

        }

        @Override
        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
            log.info("超时事件时触发:{}", JSONObject.toJSONString(evt));
            log.info("超时事件时触发:{}", evt.getClass());
            log.info("超时事件时触发:{}", evt);
//            new SslCloseCompletionEvent
            log.debug("-----发送心跳");
            ctx.writeAndFlush(new PingWebSocketFrame());
            if (evt instanceof IdleStateEvent) {
                IdleStateEvent event = (IdleStateEvent) evt;
                log.info("evt:{}",evt);
                // 当我们长时间没有给服务器发消息时，发送ping消息，告诉服务器我们还活跃
                if (event.state().equals(IdleState.WRITER_IDLE)) {
                    log.debug("发送心跳");
//                    ctx.writeAndFlush(new TextWebSocketFrame("{\"op\":1}"));
                }
            } else {
                super.userEventTriggered(ctx, evt);
            }
        }

        /**
         * 发送给服务器消息的方法
         * @param ctx
         * @throws Exception
         */
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("与服务端连接成功");
            ctx.fireChannelActive();

            handlerAdded(ctx);
            String message = "{\"op\":2,\"d\":{\"token\":\"Bot 102082942.2LLRU234TSXVfOVGg02Ht8Eab83lxu7I\",\"intents\":513,\"shard\":[0,1]}}";
//            ctx.writeAndFlush(Unpooled.copiedBuffer(message, CharsetUtil.UTF_8));
//            ctx.writeAndFlush(new TextWebSocketFrame(message));

        }
        /**
         * 在处理过程中引发异常时被调用
         * @param ctx
         * @param cause
         * @throws Exception
         */
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            System.out.println("异常关闭"+cause.getMessage());
            if (heartBeat != null) {
                heartBeat.cancel(true);
                heartBeat = null;
            }
            cause.printStackTrace();
            ctx.close();
        }

        /**
         * 非活跃状态，没有连接远程主机的时候。
         *
         * @param ctx
         * @throws Exception
         */
        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("主机关闭");

        }

        public void handlerAdded(ChannelHandlerContext ctx) {
            this.handshakeFuture = ctx.newPromise();
        }

        public WebSocketClientHandshaker getHandshaker() {
            return handshaker;
        }

        public void setHandshaker(WebSocketClientHandshaker handshaker) {
            this.handshaker = handshaker;
        }

        public ChannelPromise getHandshakeFuture() {
            return handshakeFuture;
        }

        public void setHandshakeFuture(ChannelPromise handshakeFuture) {
            this.handshakeFuture = handshakeFuture;
        }

        public ChannelFuture handshakeFuture() {
            return this.handshakeFuture;
        }

    }


    /**
     * 定义心跳任务线程
     *
     * @author Administrator
     *
     */
    public static class HeartBeatTask implements Runnable {
        private final ChannelHandlerContext ctx;

        public HeartBeatTask(final ChannelHandlerContext ctx) {
            this.ctx = ctx;
        }

        @Override
        public void run() {
            try {
                //发送心跳报文
                ctx.writeAndFlush(new TextWebSocketFrame("{\"op\":1}"));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


}